pub mod class;
use std::collections::HashSet;

pub use class::FmtClass;
pub mod interface;
pub use interface::FmtInterface;

use crate::access::Access;
use crate::attr::Attribute;
use crate::constant::Constant;
use crate::field::Field;
use crate::method::Method;
use crate::reader::Stream;
use crate::Config;

#[derive(Debug)]
pub struct Class {
	#[allow(dead_code)]
	magic: u32,
	#[allow(dead_code)]
	minor_version: u16,
	#[allow(dead_code)]
	major_version: u16,
	pub(crate) access: Access,
	pub(crate) this_: String,
	pub(crate) super_: String,
	pub(crate) faces: Vec<String>,
	pub(crate) fields: Vec<Field>,
	pub(crate) methods: Vec<Method>,
	pub(crate) _attrs: Vec<Attribute>,
}

impl Class {
	pub fn parse(data: &[u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		let magic = s.read().filter(|v| *v == 0xCAFEBABE)?;
		let minor_version = s.read()?;
		let major_version = s.read().filter(|v: &u16| *v <= 52 && *v >= 45)?;
		let constant_count: u16 = s.read()?;
		let mut constants = vec![];
		constants.push(Constant::Empty);
		for _ in 1..constant_count {
			match constants.last() {
				Some(v) => match v {
					Constant::Double(_) | Constant::Long(_) => {
						constants.push(Constant::Empty);
						continue;
					}
					_ => (),
				},
				None => (),
			}
			constants.push(Constant::parse(&mut s)?);
		}
		let access_falgs = Access(s.read()?);
		let this_class_index: u16 = s.read()?;
		let this_class = constants.get(this_class_index as usize)?.to_string(&constants)?;
		let super_class_index: u16 = s.read()?;
		let super_class = constants.get(super_class_index as usize)?.to_string(&constants)?;
		let interface_count: u16 = s.read()?;
		let mut interfaces = vec![];
		for _ in 0..interface_count {
			let index: u16 = s.read()?;
			let name = constants.get(index as usize)?.to_string(&constants)?;
			interfaces.push(name);
		}
		let field_count: u16 = s.read()?;
		let mut fields = vec![];
		for _ in 0..field_count {
			let field = Field::parse(&mut s, &constants)?;
			fields.push(field);
		}
		let method_count: u16 = s.read()?;
		let mut methods = vec![];
		let mut method_names: HashSet<String> = Default::default();
		for _ in 0..method_count {
			let mut method = Method::parse(&mut s, &constants)?;
			if method_names.contains(&method.rust_name) {
				for i in 2..1000 {
					let new_name = format!("{}_v{}", &method.rust_name, i);
					if method_names.contains(&new_name) {
						continue;
					}
					method.rust_name = new_name.clone();
					method_names.insert(new_name);
					break;
				}
			} else {
				method_names.insert(method.rust_name.clone());
			}
			methods.push(method);
		}
		let attr_count: u16 = s.read()?;
		let mut attrs = vec![];
		for _ in 0..attr_count {
			attrs.push(Attribute::parse(&mut s, &constants)?);
		}
		Some(Class {
			magic,
			minor_version,
			major_version,
			access: access_falgs,
			this_: this_class,
			super_: super_class,
			faces: interfaces,
			fields,
			methods,
			_attrs: attrs,
		})
	}
	pub fn fmt_consts<'a>(&'a self, pubtag: &'a str) -> FmtConsts<'a> {
		FmtConsts(&self.fields, pubtag)
	}
	pub fn fmt_fields<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtFields<'a> {
		FmtFields(&self.fields, &pubtag, &class_fun, &cfg, &self.this_)
	}
	pub fn fmt_fields_static<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtFieldsStatic<'a> {
		FmtFieldsStatic(&self.fields, &pubtag, &class_fun, &cfg, &self.this_)
	}
	pub fn fmt_methods<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtMethods<'a> {
		FmtMethods(&self.methods, pubtag, class_fun, cfg, &self.this_)
	}
	pub fn fmt_methods_static<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtMethodsStatic<'a> {
		FmtMethodsStatic(&self.methods, pubtag, class_fun, cfg, &self.this_)
	}
	pub fn fmt_faces_use<'a>(&'a self, with_object: bool) -> FmtFacesUse<'a> {
		FmtFacesUse(&self.faces, with_object)
	}
	pub fn fmt_faces_impl<'a>(&'a self, this_: &'a str, with_object: bool) -> FmtFacesImpl<'a> {
		FmtFacesImpl(&self.faces, this_, with_object)
	}
	pub fn fmt_faces_field<'a>(&'a self) -> FmtFacesField<'a> {
		FmtFacesField(&self.faces)
	}
	pub fn fmt_faces_ext<'a>(&'a self) -> FmtFacesExt<'a> {
		FmtFacesExt(&self.faces)
	}
}

pub struct FmtFacesUse<'a>(pub &'a Vec<String>, bool);
impl<'a> std::fmt::Display for FmtFacesUse<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			let name = i.replace("/", "::").replace("$", "");
			f.write_fmt(format_args!("use crate::{name};",))?;
			if self.1 {
				f.write_fmt(format_args!("use crate::{name}Object;"))?;
			}
		}
		Ok(())
	}
}

pub struct FmtFacesField<'a>(pub &'a Vec<String>);
impl<'a> std::fmt::Display for FmtFacesField<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			let name = i.split("/").last().ok_or(std::fmt::Error)?.replace("$", "");
			let snake = casbab::snake(&name);
			f.write_fmt(format_args!("__{snake}: {name}Object,"))?;
		}
		Ok(())
	}
}

pub struct FmtFacesExt<'a>(pub &'a Vec<String>);
impl<'a> std::fmt::Display for FmtFacesExt<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			let name = i.split("/").last().ok_or(std::fmt::Error)?.replace("$", "");
			f.write_fmt(format_args!("+{name}"))?;
		}
		Ok(())
	}
}

pub struct FmtFacesImpl<'a>(&'a Vec<String>, &'a str, bool);
impl<'a> std::fmt::Display for FmtFacesImpl<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			let name = i.split("/").last().ok_or(std::fmt::Error)?.replace("$", "");
			let snake = casbab::snake(&name);
			let this_ = self.1;
			f.write_fmt(format_args!(r##"impl {name} for {this_} {{}}"##))?;
			if self.2 {
				f.write_fmt(format_args!(
					r##"
impl AsRef<{name}Object> for {this_} {{
	fn as_ref(&self) -> &{name}Object {{
		&self.__{snake}
	}}
}}"##,
				))?;
			}
		}
		Ok(())
	}
}

pub struct FmtMethods<'a>(pub &'a Vec<Method>, pub &'a str, pub &'a str, pub &'a Config, pub &'a String);
impl<'a> std::fmt::Display for FmtMethods<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			if i.name.starts_with("<") {
				continue;
			}
			if !self.3.check_method(self.4, &i.name) {
				continue;
			}
			f.write_fmt(format_args!("{}\n", i.fmt_virtual(self.1, self.2, self.3)))?;
		}
		Ok(())
	}
}

pub struct FmtMethodsStatic<'a>(pub &'a Vec<Method>, pub &'a str, pub &'a str, pub &'a Config, pub &'a String);
impl<'a> std::fmt::Display for FmtMethodsStatic<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			if i.name.starts_with("<") {
				continue;
			}
			if !self.3.check_method(self.4, &i.name) {
				continue;
			}
			f.write_fmt(format_args!("{}\n", i.fmt_static(self.1, self.2, self.3)))?;
		}
		Ok(())
	}
}

pub struct FmtConsts<'a>(pub &'a Vec<Field>, pub &'a str);
impl<'a> std::fmt::Display for FmtConsts<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			if !i.is_const() {
				continue;
			}
			f.write_fmt(format_args!("{}", i.fmt_constant(self.1)))?;
		}
		Ok(())
	}
}

pub struct FmtFields<'a>(pub &'a Vec<Field>, pub &'a str, pub &'a str, pub &'a Config, pub &'a String);
impl<'a> std::fmt::Display for FmtFields<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			if i.is_const() {
				continue;
			}
			if i.access.is_static() {
				continue;
			}
			if !self.3.check_field(self.4, &i.name) {
				continue;
			}
			f.write_fmt(format_args!(
				"{}{}",
				i.fmt_get(self.1, self.2, self.3),
				i.fmt_set(self.1, self.2, self.3)
			))?;
		}
		Ok(())
	}
}
pub struct FmtFieldsStatic<'a>(pub &'a Vec<Field>, pub &'a str, pub &'a str, pub &'a Config, pub &'a String);
impl<'a> std::fmt::Display for FmtFieldsStatic<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for i in self.0.iter() {
			if i.is_const() {
				continue;
			}
			if !i.access.is_static() {
				continue;
			}
			if !self.3.check_field(self.4, &i.name) {
				continue;
			}
			f.write_fmt(format_args!(
				"{}{}",
				i.fmt_get_static(self.1, self.2, self.3),
				i.fmt_set_static(self.1, self.2, self.3)
			))?;
		}
		Ok(())
	}
}
